<!DOCTYPE html>



  


<html class="theme-next muse use-motion" lang="zh-Hans">
<head><meta name="generator" content="Hexo 3.8.0">
  <meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<meta name="theme-color" content="#222">









<meta http-equiv="Cache-Control" content="no-transform">
<meta http-equiv="Cache-Control" content="no-siteapp">
















  
  
  <link href="/lib/fancybox/source/jquery.fancybox.css?v=2.1.5" rel="stylesheet" type="text/css">







<link href="/lib/font-awesome/css/font-awesome.min.css?v=4.6.2" rel="stylesheet" type="text/css">

<link href="/css/main.css?v=5.1.4" rel="stylesheet" type="text/css">


  <link rel="apple-touch-icon" sizes="180x180" href="/images/apple-touch-icon-next.png?v=5.1.4">


  <link rel="icon" type="image/png" sizes="32x32" href="/images/favicon-32x32-next.png?v=5.1.4">


  <link rel="icon" type="image/png" sizes="16x16" href="/images/favicon-16x16-next.png?v=5.1.4">


  <link rel="mask-icon" href="/images/logo.svg?v=5.1.4" color="#222">





  <meta name="keywords" content="Hexo, NexT">





  <link rel="alternate" href="/atom.xml" title="renhao" type="application/atom+xml">






<meta name="description" content="本文总结 《深入浅出 RxJS》的要点部分。">
<meta property="og:type" content="article">
<meta property="og:title" content="【读书笔记】深入浅出 RxJS">
<meta property="og:url" content="https://huangrenhao.gitee.io/2020/09/28/read-in-depth-explanation-of-rxjs/index.html">
<meta property="og:site_name" content="renhao">
<meta property="og:description" content="本文总结 《深入浅出 RxJS》的要点部分。">
<meta property="og:locale" content="zh-Hans">
<meta property="og:updated_time" content="2020-11-10T01:51:17.000Z">
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="【读书笔记】深入浅出 RxJS">
<meta name="twitter:description" content="本文总结 《深入浅出 RxJS》的要点部分。">



<script type="text/javascript" id="hexo.configurations">
  var NexT = window.NexT || {};
  var CONFIG = {
    root: '/',
    scheme: 'Muse',
    version: '5.1.4',
    sidebar: {"position":"left","display":"post","offset":12,"b2t":false,"scrollpercent":false,"onmobile":false},
    fancybox: true,
    tabs: true,
    motion: {"enable":true,"async":false,"transition":{"post_block":"fadeIn","post_header":"slideDownIn","post_body":"slideDownIn","coll_header":"slideLeftIn","sidebar":"slideUpIn"}},
    duoshuo: {
      userId: '0',
      author: '博主'
    },
    algolia: {
      applicationID: '',
      apiKey: '',
      indexName: '',
      hits: {"per_page":10},
      labels: {"input_placeholder":"Search for Posts","hits_empty":"We didn't find any results for the search: ${query}","hits_stats":"${hits} results found in ${time} ms"}
    }
  };
</script>



  <link rel="canonical" href="https://huangrenhao.gitee.io/2020/09/28/read-in-depth-explanation-of-rxjs/">





  <title>【读书笔记】深入浅出 RxJS | renhao</title>
  








<link rel="stylesheet" href="/css/prism-tomorrow.css" type="text/css"></head>

<body itemscope="" itemtype="http://schema.org/WebPage" lang="zh-Hans">
  
  
    
  

  <div class="container sidebar-position-left page-post-detail">
    <div class="headband"></div>

    <header id="header" class="header" itemscope="" itemtype="http://schema.org/WPHeader">
      <div class="header-inner"><div class="site-brand-wrapper">
  <div class="site-meta ">
    

    <div class="custom-logo-site-title">
      <a href="/" class="brand" rel="start">
        <span class="logo-line-before"><i></i></span>
        <span class="site-title">renhao</span>
        <span class="logo-line-after"><i></i></span>
      </a>
    </div>
      
        <p class="site-subtitle"></p>
      
  </div>

  <div class="site-nav-toggle">
    <button>
      <span class="btn-bar"></span>
      <span class="btn-bar"></span>
      <span class="btn-bar"></span>
    </button>
  </div>
</div>

<nav class="site-nav">
  

  
    <ul id="menu" class="menu">
      
        
        <li class="menu-item menu-item-home">
          <a href="/" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-home"></i> <br>
            
            首页
          </a>
        </li>
      
        
        <li class="menu-item menu-item-categories">
          <a href="/categories/" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-th"></i> <br>
            
            分类
          </a>
        </li>
      
        
        <li class="menu-item menu-item-archives">
          <a href="/archives/" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-archive"></i> <br>
            
            归档
          </a>
        </li>
      

      
    </ul>
  

  
</nav>



 </div>
    </header>

    <main id="main" class="main">
      <div class="main-inner">
        <div class="content-wrap">
          <div id="content" class="content">
            

  <div id="posts" class="posts-expand">
    

  

  
  
  

  <article class="post post-type-normal" itemscope="" itemtype="http://schema.org/Article">
  
  
  
  <div class="post-block">
    <link itemprop="mainEntityOfPage" href="https://huangrenhao.gitee.io/2020/09/28/read-in-depth-explanation-of-rxjs/">

    <span hidden itemprop="author" itemscope="" itemtype="http://schema.org/Person">
      <meta itemprop="name" content="renhao">
      <meta itemprop="description" content="">
      <meta itemprop="image" content="/images/avatar.gif">
    </span>

    <span hidden itemprop="publisher" itemscope="" itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="renhao">
    </span>

    
      <header class="post-header">

        
        
          <h1 class="post-title" itemprop="name headline">【读书笔记】深入浅出 RxJS</h1>
        

        <div class="post-meta">
          <span class="post-time">
            
              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>
              
                <span class="post-meta-item-text">发表于</span>
              
              <time title="创建于" itemprop="dateCreated datePublished" datetime="2020-09-28T10:54:34+08:00">
                2020-09-28
              </time>
            

            

            
          </span>

          
            <span class="post-category">
            
              <span class="post-meta-divider">|</span>
            
              <span class="post-meta-item-icon">
                <i class="fa fa-folder-o"></i>
              </span>
              
                <span class="post-meta-item-text">分类于</span>
              
              
                <span itemprop="about" itemscope="" itemtype="http://schema.org/Thing">
                  <a href="/categories/读书笔记/" itemprop="url" rel="index">
                    <span itemprop="name">读书笔记</span>
                  </a>
                </span>

                
                
              
            </span>
          

          
            
              <span class="post-comments-count">
                <span class="post-meta-divider">|</span>
                <span class="post-meta-item-icon">
                  <i class="fa fa-comment-o"></i>
                </span>
                <a href="/2020/09/28/read-in-depth-explanation-of-rxjs/#comments" itemprop="discussionUrl">
                  <span class="post-comments-count valine-comment-count" data-xid="/2020/09/28/read-in-depth-explanation-of-rxjs/" itemprop="commentCount"></span>
                </a>
              </span>
            
          

          
          

          

          
            <div class="post-wordcount">
              
                
                <span class="post-meta-item-icon">
                  <i class="fa fa-file-word-o"></i>
                </span>
                
                  <span class="post-meta-item-text">字数统计&#58;</span>
                
                <span title="字数统计">
                  15.2k
                </span>
              

              
                <span class="post-meta-divider">|</span>
              

              
                <span class="post-meta-item-icon">
                  <i class="fa fa-clock-o"></i>
                </span>
                
                  <span class="post-meta-item-text">阅读时长 &asymp;</span>
                
                <span title="阅读时长">
                  55
                </span>
              
            </div>
          

          

        </div>
      </header>
    

    
    
    
    <div class="post-body" itemprop="articleBody">

      
      

      
        <p>本文总结 《深入浅出 RxJS》的要点部分。<a id="more"></a></p>
<p>RxJS兼具函数式和响应式两种编程方式的特点，擅长处理异步操作，因为它对数据采用“推”的处理方式，当一个数据产生的时候会被推送给对应的处理函数，这个处理函数不用关心数据是同步产生还是异步产生的，这样把开发者从命令式异步处理的枷锁中解放了出来。</p>
<p>RxJS可以在控制大量代码的复杂度、保持代码可读、处理异步操作等问题上提供一种解决思路。</p>
<p><a href="https://github.com/mocheng/dissecting-rxjs" target="_blank" rel="noopener">代码库</a></p>
<h2 id="第一章-函数响应式编程"><a href="#第一章-函数响应式编程" class="headerlink" title="第一章 函数响应式编程"></a>第一章 函数响应式编程</h2><p>RxJS中有一种特殊的对象，称为“流”，或称为“数据流”或“Observable对象”（可以将其理解为一条河流，数据就是这条河流中的水）。代表“流”的变量标志符都是用<code>$</code>符号结尾（RxJS编程中普遍风格，称为“芬格兰命名法”）。<a href="https://gitee.com/huangrenhao/jd/tree/master/readingNotes-master/In-depth_explanation_of_RxJS/dissecting-rxjs/chapter-01/rxjs/timingSenseTest.js" target="_blank" rel="noopener">如此例中</a><code>mouseDown$</code>与<code>mouseUp$</code>都是数据流，分别代表按钮上mousedown事件和mouseUp事件集合，不光包含已经发生的事件还包含没有发生的鼠标事件。“流”可以通过多种方式创建，<code>mouseDown$</code>,<code>mouseUp$</code>是通过<code>fromEvent</code>函数从网页的DOM元素中获得，<code>holdTime$</code>通过<code>mouseDown$</code>与<code>mouseUp$</code>计算衍生而来。流对象中的数据可以通过<code>subscribe</code>函数操作。</p>
<h3 id="函数式编程"><a href="#函数式编程" class="headerlink" title="函数式编程"></a>函数式编程</h3><p>函数式编程对函数使用的要求：声明式、纯函数、数据不可变性</p>
<p>JavaScript并没有强制要求数据不可变性同时JavaScript函数也不能保证没有副作用。虽然JavaScript不能算是一个纯粹意义上的函数式编程语言，但可通过编程规范再借助工具（RxJS）可以使用JavaScript写出函数式的代码。</p>
<ul>
<li><p>声明式：与声明式相对应的编程方式为命令式编程。例如操作数组的每个元素并返回一个新数组时，命令式编程：创建一个新数组，然后遍历原数组并计算赋值（例如实现数组中每个元素加倍和数组中每个元素加1产生了重复代码-for循环）；声明式编程：构建一个规则，这个过程就变成构建一个f函数作用在数组上，然后返回新数组（将这种模式抽象出来）</p>
</li>
<li><p>纯函数：满足以下两个条件的函数</p>
<ul>
<li>函数的执行过程完全由输入参数决定，不会受除参数之外的任何数据影响</li>
<li>函数不会修改任何外部状态，比如修改全局变量或传入的参数对象</li>
</ul>
<p>“不纯函数”可能的情况：读取用户输入（调用了<code>alert</code>或者<code>confirm</code>函数）、抛出一个异常、网络输入/输出操作（通过AJAX调用一个服务器的API）、操作浏览器的DOM等。</p>
<p>纯函数可以由输入的参数得到特定、可以预计的输出。</p>
</li>
<li><p>数据不可变：需要数据状态发生改变时保持原有数据不变，产生一个新的数据来体现这种变化。</p>
</li>
</ul>
<p>函数式编程与面向对象编程：面向对象的方法把状态的改变封装起来以达到让代码清晰的目的，函数式编程则是尽量减少变化的部分让代码的逻辑清晰。面向对象将数据封装在类的实例对象中，限制对数据的访问方式但隐藏了数据的修改历史（带来了不确定性，代码可维护性下降）。</p>
<h3 id="响应式编程"><a href="#响应式编程" class="headerlink" title="响应式编程"></a>响应式编程</h3><p>响应式编程：书中使用了Excel的求和公式作为实例，通过选中特定单元格进行求和公式的编写就是一次响应式编程，用户的输入可以看作一个数据流，这个流中的一个元素是对某个格子内的数值的修改。虽然用户的输入操作不可预料但是程序一样能够处理。</p>
<p>响应式编程中的框架Reactive Extension（ReactiveX或Rx）：Rx诞生的主要目的是为了解决异步处理的问题（但同样适合同步的数据处理）。</p>
<hr>



<h2 id="第二章-RxJS入门-以V5版本为例"><a href="#第二章-RxJS入门-以V5版本为例" class="headerlink" title="第二章 RxJS入门(以V5版本为例)"></a>第二章 RxJS入门(以V5版本为例)</h2><h3 id="Observable和Observer"><a href="#Observable和Observer" class="headerlink" title="Observable和Observer"></a>Observable和Observer</h3><p>Observable，可以被观察的对象即可被观察者，而Observer就是观察者，连接两者的桥梁就是Observable对象的函数<code>subscribe</code>。Observable实现了两种设计模式：</p>
<ul>
<li><p>观察者模式（Observer Pattern）：分而治之的思想（在一个持续产生事件的系统中如何分隔功能，让不同的模块只需要处理一部分逻辑），解决方法是将逻辑分为发布者（Publisher）和观察者（Observer），发布者只负责产生事件，它会通知所有注册挂上号的观察者而不关心这些观察者如何处理这些事件；观察者可以被注册到某个发布者，只管接收到事件后处理而不关心这些数据如何产生。两者之间的关系简单的说就是观察者订阅发布者，发布者向观察者推送数据。发布者负责产生事件（RxJS中Observable对象的工作），观察者响应事件（RxJS中subscribe函数的参数），发布者与观察者之间的关联（调用subscribe）。</p>
<pre><code>import { Observable } from &#39;rxjs/Observable&#39;;
import &#39;rxjs/add/observable/of&#39;;

const source$ = Observable.of(1,2,3);
source$.subscribe(console.log);
</code></pre><p>RxJS中Observable对象就是一个发布者，通过Observable对象的<code>subscribe</code>函数就可以将这个发布者和某个观察者（Observer）连接起来。这里Observeable对象<code>source$</code>就是一个发布者，<code>console.log</code>就是一个观察者。</p>
</li>
<li><p>迭代器模式：迭代器（Iterator）指的是能够遍历一个数据集合的对象，提供一个通用的接口让使用者完全不用关心这个数据结构的具体实现方式。迭代器也称为游标（类似于移动的指针），从集合中的一个元素移动到另一个元素，完成对整个集合的遍历。</p>
<blockquote>
<p>编程中的“拉”、“推”是从数据消费者的角度描述。最常见的例子是网页中的请求，“拉”就是网页主动通过AJAX请求从服务器获取数据，“推”就是服务器通过websocket通道推送数据到网页中。</p>
</blockquote>
<p>“拉”式迭代器实现：通常包含以下几个函数</p>
<ul>
<li>getCurrent：获取当前被游标所指向的元素</li>
<li>moveToNext：将游标移动到下一个元素，调用之后<code>getCurrent</code>获取元素相应改变</li>
<li>isDone：判断是否遍历完所有数据</li>
</ul>
<p>RxJS中不需要主动从Observable中“拉”数据，只要subscribe上Observable对象后就能收到消息的推送。</p>
</li>
</ul>
<p>Observable对象：每个Observable对象代表的就是在一段时间范围内发生的一系列事件。公式表示<code>Observable=Publisher+Iterator</code>。</p>
<pre><code>// 简单Observable实例代码
import { Observable } from &#39;rxjs/Observable&#39;;
const onSubscribe = observer =&gt; {
    // 将数据推给observer，这里的参数observer就是“观察者”对象
    // observer对象实际上是theObserver的一个包装，可以将其理解为观察者的一个代理
    // 对observer的所有函数调用都会转移到theObserver的同名函数上去，这里相当于调
    // 用theObserver的next函数三次
    observer.next(1);
    observer.next(2);
    observer.next(3);
    // 异常情况处理
    observer.error(&#39;something wrong&#39;);
    // 代表数据流里没有更多的数据时所作的操作
    observer.complete();
};
// Observable是一个特殊类，接受一个处理Observer的函数
const source$ = new Observable(onSubscribe);
const theObserver = {
    next: item =&gt; console.log(item),
    error: err =&gt; console.log(err),
    complete: () =&gt; console.log(&#39;no more data&#39;)
};
// 在subscribe函数被调用的过程中onSubscribe才被调用
source$.subscribe(theObserver);
</code></pre><p>Observable推送数据可以有时间间隔，也可以为无穷（每次推出的数据会被observer处理不会造成数据堆积）</p>
<p>Observable和Observer之间的交流：next表示传递数据，complete表示数据传递完毕，error表示遇到异常情况。但一个Observable对象只有一种终结状态：完结（complete）或者出错（error）。故如果进入了其中一种状态（完结或出错），之后的<code>next,complete,error</code>都不会被调用。如果没有提供异常处理，发生异常时就会被系统所捕获（输出异常然后退出程序），故为了程序的健壮性需要异常处理。</p>
<p>简单形式的Observer：不创建Observer对象，直接在<code>subscribe</code>函数的参数中依次声明observer中的函数（可以使用匿名函数），函数的次序按照<code>next,err,complete</code>，如果没有则以<code>null</code>来占位。</p>
<pre><code>source$.subscribe(
    item =&gt; console.log(item),
    null,
    complete: () =&gt; console.log(&#39;no more data&#39;)
)
</code></pre><h3 id="退订Observable"><a href="#退订Observable" class="headerlink" title="退订Observable"></a>退订Observable</h3><p>Observable产生的事件只有Observer通过subscribe订阅之后才会收到，在unsubscribe之后就不会再收到（可以将<code>clearInterval(handle)</code>注释后发现因为没有调用complete所以<code>observer.next(number++)</code>一直在运行但是unsubscribe已经断开source$对象和Observer的连接，尽管一直调用next但Observer不会做出响应）。</p>
<pre><code>const onSubscribe = observer =&gt; {
    let number = 1;
    const handle = setInterval(() =&gt; {
        observer.next(number++);
    },1000);
    return {
        unsubscribe: () =&gt; {
            clearInterval(handle);
        }
    }
}
const source$ = new Observable(onSubscribe);
const subscription = source$.subscribe(item =&gt; console.log(item));

setTimeout(() =&gt; {
    subscription.unsubscribe();
},3500);
</code></pre><h3 id="Hot-Observable和Cold-Observable"><a href="#Hot-Observable和Cold-Observable" class="headerlink" title="Hot Observable和Cold Observable"></a>Hot Observable和Cold Observable</h3><p>Hot Observable：接受从订阅那一刻开始Observable产生的数据。对于Hot Observable，概念上有一个独立于Observable对象的“生产者”，这个“生产者”的创建与subscribe调用没有关系，subscribe调用只是让Observer连接上“生产者”。</p>
<p>Cold Observable：需要获取订阅之前Observable产生的数据。对于Cold Observable每一次订阅（subscribe）都产生一个“生产者”，这个生产者产生的数据通过next传递给订阅的Observer。</p>
<p>Observable称为“发布者”而不是“生产者”，因为在Hot Observable中Observable不产生数据而只是数据的搬运工。</p>
<h3 id="操作符简介"><a href="#操作符简介" class="headerlink" title="操作符简介"></a>操作符简介</h3><p>实际场景中产生Observable对象并不是每次通过直接调用Observable构造函数来创造数据流对象，也不会创造一个数据流之后就直接通过subscibe接上一个Observer，往往需要对数据流做一些处理，类似于管道操作，数据从一端流入，经过管道各个环节，然后才到达Observer。组成管道的元素就是操作符。操作符就是用来产生全新Observable对象的<strong>函数</strong>，每一个操作符都是创造一个新的Observable对象而不会对上游的Observable对象做任何修改（函数式编程中数据不可变）。</p>
<h3 id="弹珠图"><a href="#弹珠图" class="headerlink" title="弹珠图"></a>弹珠图</h3><p>用弹珠表示吐出的数据，每个弹珠之间的间隔代表的就是吐出数据之间的时间间隔，竖杠符号<code>|</code>代表数据流的完结，符号<code>X</code>代表数据流中的异常。参见<a href="https://rxmarbles.com/" target="_blank" rel="noopener">主要的操作符弹珠图</a></p>
<p>弹珠图中多条时间轴：把操作符上下游的数据流都展示出来。</p>
<hr>



<h2 id="第三章-操作符基础"><a href="#第三章-操作符基础" class="headerlink" title="第三章 操作符基础"></a>第三章 操作符基础</h2><p>对操作符使用的熟练程度决定对RxJS的掌握程度</p>
<h3 id="map和filter"><a href="#map和filter" class="headerlink" title="map和filter"></a>map和filter</h3><p>JavaScript中的map和filter：都是数组对象的成员函数、返回结果依然是数组对象、不会改变原来的数组对象。故map和filter可以链式调用，链式调用可以将一个复杂功能分解成多个小任务完成（每个小任务只需关注问题的一个方面），小任务之间的耦合度很低（数组对象本身不会被更改），代码就更容易维护。</p>
<p>操作符中的map和filter：都是Observable对象的成员函数、返回结果为Observable对象、不会改变原来的Observable对象。</p>
<h3 id="操作符分类"><a href="#操作符分类" class="headerlink" title="操作符分类"></a>操作符分类</h3><ul>
<li><p>按功能分类：</p>
<ul>
<li>创建类（creation）：没有上游，数据管道的起点</li>
<li>转化类（transformation）：对上游Observable对象中的每个数据进行转化，产生一个新的数据推送给下游</li>
<li>过滤类（filtering）：把数据流中某些数据筛选掉</li>
<li>合并类（combination）：支持多个Observable对象中的数据可以合并到同一个Observable对象中</li>
<li>多播类（multicasting）：一个Observable对象拥有多个Observer</li>
<li>错误处理类（error Handling）：支持异常错误处理</li>
<li>辅助工具类（utility）</li>
<li>条件分支类（conditional &amp; boolean）</li>
<li>数学和合计类（mathmatical &amp; aggregate）</li>
</ul>
<p>若干具有某方面特性的操作符集合：</p>
<ul>
<li><p>背压控制类（backpressure control）：背压控制类操作符就像大坝，下游对数据处理不过来的时候可以控制数据的传输减轻下游处理压力。</p>
<blockquote>
<p>背压：发大水时河流下游因为河道无法让水流及时通过，水流就会淤积逐步把压力传递给上游。</p>
</blockquote>
</li>
<li><p>可连接类（connectable）：返回的Observable对象支持connect函数，全部与多播有关。</p>
</li>
<li><p>高阶Observable（higher order observable）处理类：产生数据本身也是Observable对象的Observable对象。</p>
</li>
</ul>
</li>
<li><p>按静态和实例分类（存在形态）：</p>
<p>具体来说就是按照操作符的实现函数与Observable类的关系</p>
<p>JavaScript从诞生之初就具备面向编程的特性，面向对象事件有“类”的概念，但JavaScript中类是通过函数来实现的。参考<a href="http://huangrenhao.gitee.io/2019/09/29/js-prototypeChain/">js中的原型链和继承</a></p>
<p>这里的静态和实例区别在于一个定义在构造函数上，另一定义在原型链上。定义在构造函数上的方法通过静态方式访问即不需要实例（静态操作符），而定义在原型链上的方法依赖于实例，沿着原型链寻找同名函数（实例操作符）。</p>
<p>在链式调用中静态操作符只能出现在首位，实例操作符则可以出现在任何位置（链式调用中各级之间靠Observable对象来关联）。</p>
</li>
</ul>
<h3 id="操作符的实现"><a href="#操作符的实现" class="headerlink" title="操作符的实现"></a>操作符的实现</h3><p>操作符函数体的实现：需要考虑到下面的功能要点</p>
<ul>
<li><p>返回一个全新的Observable对象（函数式编程原则）</p>
</li>
<li><p>对上游和下游数据的订阅及退订处理：如果订阅了某个<code>Observable</code>对象但未退订可能会造成资源泄露（相关资源得不到释放）。一般的做法是在订阅上游<code>Observable</code>对象时保留返回值（这个返回值包含<code>unsubscribe</code>函数），然后再返回包含<code>unsubscribe</code>函数（这个函数中直接调用返回值的<code>unsubscribe</code>函数）的对象。</p>
</li>
<li><p>处理异常情况：对上游数据的处理中，利用<code>try</code>和<code>catch</code>组合捕获project调用的可能错误，如果有错误调用下游的error函数。</p>
<pre><code>const sub = this.subscribe({
    next: value =&gt; {
        try {
            observer.next(project(value));
        } catch(err) {
            observer.error(err);
        }
    },
    error: err =&gt; observer.error(error),
    complete: () =&gt; observer.complete(),
});
</code></pre></li>
<li><p>及时释放资源：例如对DOM中获取用户操作事件的操作符（产生的<code>Observable</code>对象被订阅会在DOM中添加事件处理函数）、和Websocket资源关联从中获取推送消息的操作符等等都需要在退订时释放资源。</p>
</li>
</ul>
<p>将函数体和Observable关联起来：</p>
<ul>
<li><p>打补丁：将静态操作符直接赋值到Observable类的某个属性上去，实例操作符复制到Observable的原型上（直接赋值到<code>Observable.prototype</code>上去，需要注意的是不能使用ES6箭头函数声明操作符函数，因为箭头函数体中<code>this</code>直接绑定与定义函数环境下的this，而不是执行时指定的this，函数体中的this就不是Observable对象本身）</p>
</li>
<li><p>使用bind绑定特定Observable：使自定义的操作符（实例操作符）只对指定的Observable对象可用（避免对已有的同名操作符造成影响）。也可以使用call，实例代码如下：</p>
<pre><code>const result$ = map.call(source$)(x =&gt; x*2);

const operator = map.bind(source$);
const result$ = operator(x =&gt; x*2);
</code></pre><p>这种模式的缺点在于上游Observable只能作为操作符函数的参数（无法用链式调用）：<code>const result$ = map.bind(map.bind(source$)(x =&gt; x*2))(x =&gt; x+1);</code>。使用绑定操作符<code>::</code>改善（可以和句点符号混用），保证函数运行时this是绑定操作符前面的对象，实例代码：</p>
<pre><code>const result$ = source$::map(x =&gt; x*2);

const result$ = source$::map(x =&gt; x*2)::map(x =&gt; x+1);

const result$ = source$::map(x =&gt; x*2).filter(x =&gt; x%3 !== 0);
</code></pre></li>
<li><p>使用lift：lift的功能是把Observable对象提升一个层次，返回一个新的Observable对象，通过传递给lift的函数参数可以赋予这个新的Observable对象特殊功能。</p>
<pre><code>// lift产生的Observable对象被订阅时，lift的参数函数就被调用，this代表Observable对象
// 参数source$代表上游Observable对象
function map(project) {
    return this.lift(fnction(source$){
        return source$.subscribe({
            next:: value =&gt; {
                try{
                    this.next(project(value));
                }catch (err) {
                    this.error(err);
                }
            },
            error: err =&gt; this.error(error),
            complete: () =&gt; this.complete(),
        });
    })
}
Observable.prototype.map = map;
</code></pre></li>
</ul>
<p>操作符的改进：操作符的函数实现部分用了this（非纯函数，未被面向函数式编程的原则），打补丁的方法中RxJS中任何操作符都是挂在Observable类上或者Observable.prototype上，这样不仅导致在打包（Tree Shaking）技术中无法识别出未调用的代码，而且会影响全局Observable类。</p>
<ul>
<li><p>call/bind的方法：一般不使用打补丁的方法导入其他操作符，替代方法：对于实例操作符使用bind/call方法，对于静态操作符直接使用产生Observable对象的函数，而不是依附于Observable的静态函数。</p>
<pre><code>// 这两个模块均未打补丁，可以认为是两个独立的函数
import {of} from &#39;rxjs/observable/of&#39;; // 静态操作符
import {map} from &#39;rxjs/operator/map&#39;; // 实例操作符

// 模块已经挂载到了Observable类或者其原型上
import {off} from &#39;rxjs/add/observable/of&#39;; // 静态操作符
import {map} from &#39;rxjs/add/operator/map&#39;;  // 实例操作符
</code></pre></li>
<li><p>lettable/pipeable操作符：call/bind方法使得Observable类本身不会被影响，但是每个操作符的函数体内仍然需要访问this。</p>
<p>let操作符：let的参数是一个函数（这个函数的功能是根据一个Observable对象产生一个新的Observable对象）。let被调用时就是把上游的Observable对象作为参数传递给let的参数（函数），然后将返回的Observable对象交给下游订阅。实例代码如下：</p>
<pre><code>import {Observable} from &#39;rxjs/Observable&#39;;
import &#39;rxjs/add/observable/of&#39;;
import &#39;rxjs/add/operator/map&#39;;
import &#39;rxjs/add/operator/let&#39;;

const source$ = Observable.of(1,2,3);

// 根据一个Observable对象产生一个新的Observable对象
const double$ = obs$ =&gt; obs$.map(x =&gt; x*2); 

const result$ = source$.let(double$);

result.subscribe(console.log);

// 不采用打补丁的方式实现map
function map (project) {
// obs$替代this，以参数的形式表示上游传入的Observable对象，纯函数
    return function(obs$) {
        return new Observable(observer =&gt; {
            return obs$.subscribe({
                next: value =&gt; observer.next(project(value)),
                error: err =&gt; observer.error(error),
                complete: () =&gt; observer.complete(),
            });
        });
    };
}
const result$ = source$.let(map(x =&gt; x*2))
</code></pre><p>lettable操作符（pipeable操作符）：能够被let当做参数来使用的操作符。静态类型操作符没有pipeable操作符的对应形式，拥有多个上游Observable对象的操作符没有pipeable操作符的对应形式。lettable操作符的使用案例：</p>
<pre><code>import { of } from &#39;rxjs/observable/of&#39;; // 导入静态操作符
import { map, filter } from &#39;rxjs/operators&#39;;      // 导入rxjs/operators/index，汇聚了所有的lettable操作符，这里都是纯函数可以通过tree shaking来进行过滤掉没有使用的代码
// import { map } from &#39;rxjs/add/operator/map.js&#39; // 这里导入的是打补丁之后的map，实际上是从rxjs/operator/map.js导入map，挂在Observable.prototype上。支持call方法的rxjs/operator/map.js是从rxjs/operators/map.js文件中导入lettable操作符，然后把this作为参数传递进去

const source$ = of(1,2,3);
// Observable类自带pipe，具备let的功能，还有管道功能
const result$ = source$.pipe(
    fileter(x =&gt; x%2 === 0),
    map(x =&gt; x*x)
);
// let 形式
// const result$ = source$.let(filter(x =&gt; x%2===0)).let(map(x =&gt; x*2));

source$.subscribe(console.log);
</code></pre><p>pipeable操作符的改名：有4个传统操作符名称和其pipeable操作符名称不同：<code>do,catch,switch,finally</code>（JavaScript关键字），作为<code>Observable.prototype</code>的某个属性可以但不能作为变量或者函数标识符。故<code>do,catch,switch,finally</code>相应的改为<code>tap,catchError,switchAll,finalize</code>。</p>
<p>管道操作符（目前还是提议）：<code>|&gt;</code>，竖杠和大于号的组合，用来连接两个值，前一个值没有任何要求，后一个值必须是函数，作用就是把前面的值作为参数来调用后面的函数。</p>
<pre><code>// 实例1： 666 &gt;| foo |&gt; bar 等同于bar(foo(666))
const result$ = source$
    |&gt; filter (x =&gt; x%2 ==0)
    |&gt; map ( x =&gt; x*2)
</code></pre></li>
</ul>
<hr>



<h2 id="第四章-创建数据流"><a href="#第四章-创建数据流" class="headerlink" title="第四章 创建数据流"></a>第四章 创建数据流</h2><h3 id="创建类操作符"><a href="#创建类操作符" class="headerlink" title="创建类操作符"></a>创建类操作符</h3><p>通过传递一个函数（控制Observable的行为）给构造函数使用new关键词来创建Observable对象定制性很高。可以将一些固定模式抽象出来重复利用。</p>
<p>创建类操作符：不依赖与其他Observable对象，可以凭空或者根据其他数据源创造一个Observable对象的方法。故大部分都是静态操作符。</p>
<h3 id="创建同步数据流的操作符"><a href="#创建同步数据流的操作符" class="headerlink" title="创建同步数据流的操作符"></a>创建同步数据流的操作符</h3><p>需要关心产生哪些数据以及数据之间的先后顺序（不需要考虑数据之间的时间间隔问题）</p>
<ul>
<li><p>create：直接调用Observable的构造函数（直接定义在Observable类上，不需导入直接使用）</p>
<pre><code>Observable.create = function(subscribe) { 
    return new Observable(subscribe);
}
</code></pre></li>
<li><p>of：列举数据。创建指定数据集合的Observable对象。Observable对象被订阅时吐出数据的过程是同步的，按照其在参数列表的先后顺序吐出且彼此之间没有时间间隔，且完毕之后调用Observer的complete函数。of产生的是cold Observable。可以通过打补丁的方式调用或者不使用打补丁直接使用。</p>
<pre><code>const source$ = Observable.of(1,2,3); // import &#39;rxjs/add/observable/of&#39;;
const source$ = of(1,2,3);              // import { of } from &#39;rxjs/observable/of&#39;;
</code></pre></li>
<li><p>range：指定范围。指定一个范围的开始值和长度就可以产生这个范围内的数字序列（每次递增1，开始值可以为任何数字）。以同步的方式吐出数据（依次且无时间间隔），完毕之后调用Observer的complete函数。</p>
</li>
<li><p>generate：循环创建。类似for循环，设定一个初始值，每次递增这个值，直到满足某个条件后终止循环，同时循环体中可以根据当前值产生数据（不限于数值序列的数据流）。</p>
<pre><code>const source$ = Observable.generate(
    2, // 初始值，相当于for循环中i=2
    value =&gt; value&lt;10, // 循环条件，相当于for循环中的条件判断
    value =&gt; value+2,  // 每次递增的值
    value =&gt; value*value // 产生的结果
);
</code></pre></li>
<li><p>repeat（实例操作符）：重复数据的数据流。可以重复上游Observer中的数据若干次。只有在上游Observable对象完结（complete）之后才会重新订阅（在完结之前不知道会不会有新的数据从上游推送下来，如果上游对象没有完结不会重新订阅），是一个不断订阅、退订指定次数的重复过程。</p>
</li>
<li><p>empty：产生一个直接完结的Observable对象，无参数，不产生任何数据。</p>
</li>
<li><p>throw：直接出错，抛出的错误为throw的参数。</p>
</li>
<li><p>never：不吐出数据、不完结、不产生错误，直到永远。</p>
</li>
</ul>
<h3 id="创建异步数据流的操作符"><a href="#创建异步数据流的操作符" class="headerlink" title="创建异步数据流的操作符"></a>创建异步数据流的操作符</h3><ul>
<li><p>interval和timer：定时产生数据。</p>
<p>interval接受一个数值类型的参数（代表产生数据的间隔毫秒数），返回的Observable对象按照这个时间间隔输出递增的整数序列（从0开始）。不会主动调用下游的complete，停止这个序列需要作出退订操作。相当于setInterval。</p>
<p>timer接受两个参数，一个数值类型或者Date类型的参数（代表要产生数据的时间点），数值类型代表毫秒数，产生的Observable对象会在指定毫秒之后吐出一个数据0然后立刻完结（未指定第二个参数）。第二个参数使用时就会产生一个持续吐出数据的Observable对象（类似interval数据流），第二个参数指定的是各数据之间的时间间隔（但从被订阅到产生第一个数据0的时间间隔仍由第一个参数决定）。</p>
</li>
<li><p>from：可把一切转化为Observable。from可以把任何对象都转化为Observable对象。只要from接受的参数像Observable就行，可以根据参数中的数据产生一个真正的Observable对象。例如可以将数组、字符串、实现了一种Iterable类型的generator等都转化为Observable。</p>
</li>
<li><p>fromPromise：异步处理的交接。如果from的参数是Promise对象，那么这个Promise成功结束from产生的Observable对象就会吐出Promise成功的结果并且立刻结束。当Promise对象以失败而告终时from产生的Observable对象也会立刻产生失败事件。</p>
</li>
<li><p>fromEvent：最常见的用法就是把DOM中的事件转化为Observable对象中的数据，也可以从Node.js的events获得数据。fromEvent的第一个参数是一个事件源（浏览器中最常见的事件源就是特定的DOM元素，Node中通过EventEmitter构造的具体事件发生器实例），第二个参数是事件的名称（对应DOM事件就是click、mousemove这样的字符串，node中就是事件发生器实例emit函数发送的事件名称）。fromEvent产生的是hot Observable。</p>
</li>
<li><p>fromEventPattern：接受两个函数参数，分别对应产生的Observable对象被订阅和退订时的动作，即当Observable对象被subscribe时第一个函数参数被调用，被unsubscribe时第二个函数参数调用。</p>
</li>
<li><p>ajax：根据AJAX请求的返回结果产生Observable对象。</p>
</li>
<li><p>repeatWhen：接受一个函数作为参数，这个函数在上游第一次产生异常时被调用，然后这个函数应该返回一个Observable对象，这个对象就是一个控制器（控制的是时间节奏，与吐出来的值无关），作用就是控制repeatWhen何时重新订阅上游，当控制器Observable吐出一个数据的时候，repeatWhen就会做退订上游并重新订阅的动作。</p>
</li>
<li><p>defer：接受一个函数作为参数，当defer产生的Observable对象被订阅时defer的函数参数就会被调用，预期这个函数会返回另一个Observable对象。即difer转嫁所有工作的对象。</p>
<blockquote>
<p>一方面希望Observable不要太早创建（数据源头的Observable需要占用资源），另一方面又希望Observable尽早创建（方便代码）。解决方案就是仍然创建一个Observable，但这个Observable只是一个代理，在创建之时并不会做分配资源的工作，只有当被订阅时才会去创建真正占用资源的Observable，之前产生的代理Observable会把所有工作都转交给真正占有资源的Observable。</p>
</blockquote>
</li>
</ul>
<hr>



<h2 id="第五章-合并数据流"><a href="#第五章-合并数据流" class="headerlink" title="第五章 合并数据流"></a>第五章 合并数据流</h2><h3 id="合并类操作符"><a href="#合并类操作符" class="headerlink" title="合并类操作符"></a>合并类操作符</h3><ul>
<li><p>concat（实例形式、静态方式）：首尾相连。 能够把多个Observable中的数据内容依次合并。有实例操作符方式和静态操作符方式。工作方式：首先从第一个Observable对象获取数据，把数据传给下游，实例操作符中第一个Observable就是调用concat的那个对象，静态操作符第一个Observable是concat的第一个参数；当第一个Observable对象complete之后concat就会去subscribe第二个Observable对象获取数据，把数据同样传递给下游，依次类推直到最后一个Observable完结之后，concat产生的Observable也就完结了。需要注意的是如果某个Observable对象不会完结那排在后面的Observable对象没有上场的机会。</p>
</li>
<li><p>merge（实例形式、静态方式）：先到先得快速通过。merge会第一时间订阅所有的上游Observable，然后对上游数据采取“先到先得”（多个Observable对象的数据交叉出现的方式）的策略，任何一个Observable只要有数据推下来就立刻转给下游Observable对象。merge对于同步数据流合并之后仍然是按照订阅的顺序（concat的效果，应尽量避免用于合并同步数据流）。merge有可选参数concurrent（指定可以同时合并的Observable对象个数，超出个数的Observable对象同时进行merge，除非正在进行合并的Observable完结后空出名额）。</p>
</li>
<li><p>zip（实例形式、静态方式）：拉链式组合。像拉链一样一对一合并，zip会把上游数据转化为数组形式，数组元素的个数和上游Observable对象数量相同，每个上游Observable对象都要贡献一个元素。任何一个上游的Observable没有及时吐出数据，zip的输出都要进行等待，任何一个上游的Observable完结后zip就会给下游一个complete信号（不管其余的没有完结的数据流，吐出数据最少的上游Observable决定了zip产生的数据个数）。有数据积压问题（上游数据流输出数据速度不匹配，有中空时间，需要对数据进行暂存）。默认输出数组形式，可以通过指定最后一个参数project（函数形式）让zip把所有上游的“最新数据”扔给下游之前做一下组合处理，这个函数project可以包含多个参数（每一个参数对应上游Observable的最新数据），project的返回结果就是combineLastest塞给下游的结果。</p>
</li>
<li><p>combineLatest（实例形式、静态方式）：合并最后一个数据。当任何一个上游Observable产生数据时，从所有输入Observable对象中拿到最后一次产生的数据（最新数据），然后把这些数据组合起来传给下游。只要上游不产生新的数据，combineLastest就会反复使用上游产生的最新数据。combineLastest会顺序订阅所有上游的Observable对象，只有所有上游Observable对象都已经吐出数据才会给下游传递所有上游“最新数据”组合的数据。只有所有上游Observable对象都完结之后combineLastest才会给下游一个complete信号。</p>
<pre><code>const source1$ = Observable.of(&#39;a&#39;,&#39;b&#39;,&#39;c&#39;);
const source2$ = Observable.of(1,2,3);
const result$ = source1$.combineLatest(source2$);

// 输出
// [&#39;c&#39;,1]
// [&#39;c&#39;,2]
// [&#39;c&#39;,3]
// complete

// source1$是of产生的同步数据流，在被订阅时会吐出所有的数据，在combineLastest还没来得及订阅source2$就产生了数据，所有combineLastest订阅source2$时source1$的最新数据就是&#39;c&#39;
</code></pre><p>与zip一样可以通过最后一个参数project函数或者使用map方法定制下游数据。</p>
<p>多重依赖问题：combineLastest产生的Observable对象数据依赖于上游的多个Observable对象，上游的多个Observable对象又共同依赖于另一个Observable对象。理论上输出应该是一致步调的，但实际不是，出现小缺陷（glitch）现象。</p>
</li>
<li><p>withLatestFrom：实例操作符，可以解决glitch问题。所以输入Observable的地位并不相同，调用withLatestFrom的那个Observable对象起到主导数据产生节奏的作用，作为参数的Observable对象只能贡献数据，不能控制产生数据的时机（即只有调用的Observable对象才能直接引发下游数据而参数中的Observable对象不会引发下游数据）。</p>
</li>
<li><p>race：产生的Observable完全采用第一个吐出数据的Observable对象的数据，其余的输入Observable对象则会被退订而抛弃。</p>
</li>
<li><p>startWith：实例操作符。让一个Observable对象在被订阅时总是先吐出指定的若干个数据。缺陷在于所有参数都是同步吐出，需要异步吐出参数使用concat。</p>
</li>
<li><p>forkJoin：静态操作符。接受多个Observable对象作为参数，等待所有参数Observable对象的最后一个数据（所有Observable对象都完结），将所有输入Observable对象产生的最后一个数据合并成给下游唯一的数据。</p>
</li>
</ul>
<h3 id="高阶Observable"><a href="#高阶Observable" class="headerlink" title="高阶Observable"></a>高阶Observable</h3><p>高阶Observable指的是产生的数据依然是Observable的Observable。内部的Observable不会随主干Observable的完结而完结，有自己的生命周期。数据流管理的是数据，数据流自身也可视为一种数据，数据流本身也可通过Observable来管理，让需要被管理的Observable对象成为其他Observable对象的数据。</p>
<p>操作高阶Observable的合并类操作符：concatAll、mergeAll、zipAll、combineAll，都是把一个高阶Observable的所有内部Observable都组合起来，都只有实例操作符形式。</p>
<ul>
<li><p>concatAll：和concat一样如果前一个内部Observable没有完结，concatAll就不会订阅下一个内部Observable对象。故如果上游的高阶Observable对象持续不断产生Observable对象，产生Observable对象异步于产生数据，concatAll合并的速度赶不上上游产生新的Observable对象的速度就会造成Observable积压（内存泄露）。</p>
</li>
<li><p>mergeAll：只要发现上游产生一个内部的Observable就会立刻订阅并从中抽取数据。</p>
</li>
<li>zipAll：内部产生的Observable产生的数据一对一配对出现。如果上游的高阶Observable不完结那么zipAll就不会开始工作。</li>
<li>combineAll：即combineLatest，必须上游高阶Observable完结之后才能开始给下游产生数据，因为只有确定作为输入的内部Observable对象的个数才能拼凑出第一个传递给下游的数据。</li>
</ul>
<p>concatAll中数据积压问题的解决：舍弃数据（很多场景下并不需要无损的数据流连接）。</p>
<ul>
<li><p>switch：总是切换到最新的内部Observable对象获取数据。switch产生的Observable对象完结：上游高阶Observable（产生新的内部Observable对象）和当前内部Observable（产生数据）都完结。</p>
</li>
<li><p>exhaust：在耗尽当前内部Observable的数据之前不会切换到下一个内部Observable对象。可能会造成内部Observable被舍弃（产生的时候前一个内部Observable未完结）。exhaust产生的Observable对象完结：最新的内部Observable对象完结且上游高阶Observable对象完结。</p>
</li>
</ul>
<hr>



<h2 id="第六章-辅助类操作符"><a href="#第六章-辅助类操作符" class="headerlink" title="第六章 辅助类操作符"></a>第六章 辅助类操作符</h2><h3 id="数学类操作符"><a href="#数学类操作符" class="headerlink" title="数学类操作符"></a>数学类操作符</h3><ul>
<li>count：统计上游Observable对象吐出的所有数据个数（适用于异步）。</li>
<li>max、min：max取得上游Observable对象的最大值，min为最小值。max和min都可以接受比较函数参数作为对复杂类型大小的比较方法。</li>
<li>reduce：和JavaScript数组的reduce功能相同。</li>
</ul>
<h3 id="条件布尔类操作符"><a href="#条件布尔类操作符" class="headerlink" title="条件布尔类操作符"></a>条件布尔类操作符</h3><p>根据上游Observable对象的某些条件产生一个新的Observable对象。</p>
<ul>
<li>every：一个判定函数作为参数，上游Observable吐出的每个数据都会被这个判定函数检验，只有所有数据的结果都为true时上游的Observable对象完结后every产生的新Observable对象就会吐出一个true。只要其中任一数据为false直接吐出false（不会等到上游Observable完结）。</li>
<li>find、findIndex：找到上游Observable对象中满足判定条件的第一个数据（find会吐出找到的上游数据，没有则为undefined。findIndex会吐出满足判定条件的数据序号，没有则为-1），产生的Observable对象在吐出数据后立即完结。</li>
<li>isEmpty：检查上游Observable对象是不是“空的”（Observable没有吐出任何数据就完结），上游Observable吐出数据后isEmpty返回的Observable对象会吐出true后完结。</li>
<li>defaultEmpty：检测上游Observable对象是否为空，接受一个参数（上游Observable对象为空时将这个参数图给下游，不为空就将上游Observable对象吐出的东西转交给下游）</li>
</ul>
<hr>



<h2 id="第七章-过滤数据流"><a href="#第七章-过滤数据流" class="headerlink" title="第七章 过滤数据流"></a>第七章 过滤数据流</h2><h3 id="过滤类操作符的模式"><a href="#过滤类操作符的模式" class="headerlink" title="过滤类操作符的模式"></a>过滤类操作符的模式</h3><p>最基本的功能就是对一个给定数据流中每个数据判断是否满足某个条件，如果满足条件就可以传递给下游否则就抛弃掉。判断一个数据是否有资格进入下游使用的是“判定函数”，几乎所有的过滤类操作符都有判定函数参数，有的操作符还可接受“结果选择器”函数参数（定制传输给下游的数据）。</p>
<ul>
<li><p>filter：选择符合判定函数的数据</p>
</li>
<li><p>first：寻找上游Observable吐出数据中满足判定函数的第一个数据，没有判定函数参数相当于寻找上游Observable吐出的第一个数据，然后立即完结。first有结果选择器函数参数。first找不到满足判定条件的值就会向下游传一个EmptyError（可以通过参数指定在找不到满足判定条件向下游传递的值）。</p>
</li>
<li><p>last：寻找上游Observable中最后一个满足判定条件的数据（需要等待上游Observable完结）。第二个参数是结果选择器参数，第三个是默认值参数。</p>
</li>
<li><p>take：从上游Observable拿指定数量的数据。</p>
<ul>
<li><p>takeLast：相当于可以获取多个数据的last。take产生的Observable对象数据产生时刻和上游Observable是一致的，takeLast只有确定上游数据完结的时候才产生数据（一次性产生所有数据并传递给下游）。</p>
</li>
<li><p>takeWhile：take不支持判定函数作为参数，takeWhile接受一个判定函数作为参数，判定函数有上游的数据和对应的序号两个参数，只要判定函数返回false，takeWhile产生的Observable就完结。</p>
</li>
<li><p>takeUntil：用Observable对象来控制另一个Observable对象的数据产生。实例：</p>
<pre><code>const source$ = Observable.interval(1000);
const notifier$ = Observable.timer(2500);
const takeUntil$ = source$.takeUntil(notifier$);// 每隔一秒吐出一个递增整数，在2.5秒之后完结。
</code></pre></li>
</ul>
</li>
<li><p>skip：忽略上游Observable吐出的前n个数据（由skip参数指定），从n+1个数据开始，上游Observable完结skip产生的Observable跟着完结。同take类似有skipWhile和skipUtil。skipWhile只要上游某个数据让判定函数返回false，skipWhile就不做跳过的动作（所有上游数据都转手给下游）。</p>
</li>
</ul>
<h3 id="回压控制"><a href="#回压控制" class="headerlink" title="回压控制"></a>回压控制</h3><p>数据管道中某个环节数据涌入的速度超过了处理速度。有损回压控制：舍弃掉一些数据让输入和处理的速度平衡。</p>
<ul>
<li>throttleTime和debounceTime：throttleTime的作用是限制在duration时间范围内，从上游传递给下游数据的个数；debounceTime的作用是传递给下游的数据间隔不能小于给定的时间dueTime。</li>
<li>throttle与debounce：与throttleTime和debounceTime的区别在于不带Time的操作符使用Observable中的数据来控制流量而不是时间。</li>
<li>audit与auditTime：可以认为audit是做throttle类似工作，不同的是在“节流时间”范围内throttle把第一个数据传给下游，audit是把最后一个数据传给下游。</li>
<li>sampleTimesampleTime不管上游Observable对象产生数据的节奏，根据指定的参数毫秒数来给下游传递数据，每次都是在时间块结尾将这个时间块内上游的最后一个数据推给下游（auditTime的时间块的开始是由上游产生数据触发，而sampleTime的时间块开始和上游无关）。如果sampleTime某个时间块内上游没有传递数据则那个时间块结尾也不会向下游传递数据。</li>
<li>sample：sample要根据规则在一个范围内取一个数据，抛弃其他数据。sample的参数是一个简单的Observable对象（不是返回Observable对象的函数），当这个Observable对象产生一个数据的时候就从上游取出最后产生的数据传给下游。</li>
</ul>
<p>以上带Time后缀的操作符都是根据时间来选取数据并抛弃一些上游数据，<code>throttle,debounce,audit</code>可以根据数据来决定回压控制的节奏（只有一个数据参数，无法综合考虑多个上游数据）。不适用于不希望处理重复上游数据的场景（例如处理某个连续出现的数据，并且这种数据序列与时间无关）。</p>
<ul>
<li>distinct：只返回从没有出现过的数据（上游同样的数据只有第一次产生时才会传给下游）。distinct第二个可选参数可以是一个Observable对象（这个对象每次产生数据，都会清空distinct维护的“唯一数据集合”，用于判断数据是否出现过，故这样做的缺点就是数据只能在一段时间内是唯一的。主要的目的是避免内存泄露）。</li>
<li>distinctUntilChanged：只过滤掉上游中连着重复出现的数据（和上游中上一个数据进行比较）。提供一个可选参数用来定制比较两个对象是否相等的逻辑。</li>
<li>distinctUntilKeyChanged：参数中指定用来比较的字段。可以由distinctUntilChanged实现。</li>
</ul>
<p>其他过滤方式：</p>
<ul>
<li>ignoreElements：忽略上游产生的数据，只关心complete和error事件。可以由filter实现。</li>
<li>elementAt：把上游数据当数组，只获取指定下标的那一个数据。使用first配合函数参数也可实现。第二个参数可以指定没有对应下标数据时的默认值。</li>
<li>single：用来检查上游中是否只有一个满足对应条件的数据，是则向下游传递这个数据，否则向下游传递一个异常。</li>
</ul>
<hr> 



<h2 id="第8章-转化数据流"><a href="#第8章-转化数据流" class="headerlink" title="第8章 转化数据流"></a>第8章 转化数据流</h2><h3 id="映射数据"><a href="#映射数据" class="headerlink" title="映射数据"></a>映射数据</h3><p>A-&gt;f(A)</p>
<ul>
<li>map：第一个参数为函数参数project，第二个参数为可选参数指定project执行时的this值。mapTo、pluck都可由map实现。</li>
<li>mapTo：将上游数据原封不动地传递给下游。</li>
<li>pluck：将上游中特定字段的值取出，pluck的参数就是字段的名字。多个参数用于访问嵌套的属性<code>pluck（&#39;target&#39;,&#39;tagName&#39;)-&gt;target.tagName</code>。</li>
</ul>
<h3 id="缓存窗口：无损回压控制"><a href="#缓存窗口：无损回压控制" class="headerlink" title="缓存窗口：无损回压控制"></a>缓存窗口：无损回压控制</h3><p>无损回压控制就是把上游在一段时间内产生的数据放到一个数据集合里，然后把这个数据集合（数组、Observable对象）一次性丢给下游。支持数组的以buffer开头，支持Observable对象的以window开头。</p>
<ul>
<li>bufferTime和windowTime：根据时间来缓存上游数据，用一个参数指定产生缓冲窗口的间隔。windowTime把上游数据传递出去不需要延迟，bufferTime需要缓存上游的数据。可选的第二个参数指定每个时间区块开始的时间间隔。若小于第一个参数则内部Observable可能会出现重叠，若大于第一个参数则可能会造成数据丢失。第三个参数指定每个时间区间内缓存的最多数据个数。</li>
<li>windowCount和bufferCount：根据数据个数来界定区间。可选的第二个参数指定多少个数为一个区间，每个区间的数据个数由第一个值指定。</li>
<li>windowWhen和bufferWhen：接受一个返回Observable对象的无参数函数作为参数，用于控制上游的数据分割，每当返回的Observable对象产生数据或者完结时，windowWhen就认为是一个缓冲区块的结束并重新开启一个缓冲窗口。</li>
<li>windowToggle和bufferToggle：第一个参数为一个Observable对象，第二个参数为一个函数closingSelector，每当这个Observable对象产生一个数据代表一个缓冲窗口的开始，同时closingSelector也会被调用，用来获得缓冲窗口结束的通知，closingSelector的参数就是第一个参数Observable对象产生的数据。</li>
<li>window和buffer：接受一个Observable对象参数，每当这个Observable对象产生一个数据，既是前一个缓存窗口的结束也是后一个缓存窗口的开始。这个Observable对象完结window/buffer产生的高阶Observable对象也会完结。</li>
</ul>
<p>高阶map：其函数参数project把一个数据映射为一个Observable对象，然后对每个内部Observable中的数据做组合。可以将其视为合并类操作符和map的结合。</p>
<ul>
<li>concatMap：按内部Observable对象开始时间的先后顺序连接不同内部Observable对象中的数据，就是说第二个内部Observable等到第一个Observable完结才上场等等。可以用这个操作符处理鼠标的拖拽操作。</li>
<li>mergeMap：mergeMap对于每个内部Observable对象直接合并.</li>
<li>switchMap：与mergeMap类似，但合并时候产生的内部Observable对象优先级总是更高，只要有新的内部Observable对象产生就会立刻退订之前的内部Observable对象，改为从最新的内部Observable对象拿数据。</li>
<li>exhaustMap：与switchMap正好相反，先产生的内部Observable优先级总是更高，后产生的内部Observable对象只有在之前的内部Observable对象完结后才会使用到。</li>
<li>expand：类似mergeMap，但expand传递给下游的数据也会传递给自己，逻辑死循环（除非发生异常或者expand函数参数限定某些情况不再产生数据）。</li>
</ul>
<h3 id="数据分组"><a href="#数据分组" class="headerlink" title="数据分组"></a>数据分组</h3><ul>
<li>groupBy：输出为一个高阶Observable对象，每个内部Observable对象包含上游产生的满足某个条件的数据（某个分组的数据）。可以结合DOM事件的冒泡功能来对document上的点击事件进行分组。</li>
<li>partition：利用一个判定函数作为参数，将上游Observable对象分为两个Observable对象（满足条件和不满足条件）。返回的是一个包含这两个Observable对象的数组。</li>
</ul>
<h3 id="累计数据"><a href="#累计数据" class="headerlink" title="累计数据"></a>累计数据</h3><p>累计所有上游数据。f(A,B,C)，A、B、C分别为各个上游产生的对应数据</p>
<ul>
<li>scan：参数和用法类似reduce，也有一个规约函数和一个可选的seed种子参数作为规约初始值，区别在于scan对上游每一个数据都会产生一个结果。可以用于维护应用的状态。</li>
<li>mergeScan：规约函数返回的是Observable对象。每当上游推送一个数据下来，mergeScan就调用一次规约函数，并且订阅返回的Observable对象，这个Observable对象返回的任何数据都会传给下游，mergeScan会记住传给下游的最后一个数据，当上游再次推送数据下来的时候就将最后一次传递给下游的数据作规约函数的accumulation参数。</li>
</ul>
<hr>



<h2 id="第九章-异常错误处理"><a href="#第九章-异常错误处理" class="headerlink" title="第九章 异常错误处理"></a>第九章 异常错误处理</h2><h3 id="JavaScript的异常处理"><a href="#JavaScript的异常处理" class="headerlink" title="JavaScript的异常处理"></a>JavaScript的异常处理</h3><p>try/catch只支持同步运算：错误发生，JavaScript运行环境会终止当前指令，创建一个指向产生错误指令的栈跟踪信息，包含错误信息、代码行号、代码所在文件，将这些信息封装在Error对象中，然后沿着执行栈一层一层往上找catch区块，找不到就只能交给JavaScript运行环境用默认方法处理。</p>
<p>函数回调：利用try/catch同步捕获异步操作中可能抛出的异常错误，然后通过调用回调函数将这个异常错误传递给调用者。这么做的不足之处就是可能会造成多重回调嵌套的问题（Promise可以改善）。</p>
<pre><code>const delayParse = (jsonString,callback) =&gt; {
    setTimeout(() =&gt; {
        try{
            const result = JSON.parse(jsonString);
            callback(null,result);
        }catch(error) {
            callback(error);
        }
    },1000);
};

delayParse(invalidJsonString, (error,result) =&gt; {
    if (error) {
        console.log(&#39;catch error:&#39;,error);
        return;
    }
    console.log(result);
})
</code></pre><h3 id="RxJS的异常处理"><a href="#RxJS的异常处理" class="headerlink" title="RxJS的异常处理"></a>RxJS的异常处理</h3><p>RxJS中错误异常和数据一样会沿着数据流管道从上游向下游流动，流过所有的过滤类或者转化类操作符，最后触发Observable的error方法。</p>
<p>恢复和重试：恢复就是虽然产生了异常但仍然让运算继续下去，重试就是发生错误异常的时候重新尝试之前发生错误的操作（重试之后可能获得正常的结果）。重试就是重新订阅上游Observable对象的过程，在订阅上游的同时退订上一次的订阅。</p>
<ul>
<li>catch：在管道中捕获上游传递过来的错误，主要用于恢复。接受一个函数作为参数，这个函数有两个参数，第一个为error（被捕获的错误），第二个参数caught$（代表上游Observable对象），返回一个Observable对象，产生错误或异常会打断正常的序列（发生错误或异常后的数据不会传递到下游）。</li>
<li>retry：作用就是让上游的Observable重新走一遍，达到重试的目的。接受一个数值参数指定重试的次数。</li>
<li>retryWhen：接受一个函数notifer作为参数，用于控制“重试”的节奏和次数。notifer函数有一个参数error$（一组错误组成的Observable对象），notifer返回一个Observable对象（根据这个对象决定何时重试）。</li>
<li>finally：类似于JavaScript中finally（搭配try、catch用于执行无论出错还是不出错都要做的事情）。finally的参数只在上游数据完结或者出错的时候才执行，一个数据流中finally只会发挥一次作用。</li>
</ul>
<hr>



<h2 id="第十章-多播"><a href="#第十章-多播" class="headerlink" title="第十章 多播"></a>第十章 多播</h2><p>让一个数据流的内容被多个Observe订阅。Hot Observable对应的应该是多播，因为尽管有多个subscribe，但是数据来自同一个源头，对应的cold Observable实现的就是单播。</p>
<h3 id="Subject"><a href="#Subject" class="headerlink" title="Subject"></a>Subject</h3><p>作为中间人，既有Observable的接口也具有observe的接口，Observable接口提供subscribe方法，让其他人能够订阅自己的数据源。observe接口接受推送的数据。中间人的职责就是包装之前的cold Observable对象，传递一个新的Observable对象（Hot Observable对象）。Subject有状态，这个状态就是所有Observe对象列表，后加入的观察者并不会获得加入之前Subject对象上通过next推送的数据（可以用来实现cold Observable到hot Observable的转换）。</p>
<p>makeHot操作符：makeHot返回一个Subject对象，可以实现hot Observable对象，但下游接受到的数据可能不完全是上游cold Observable产生。</p>
<p>subject对象不能重复使用，只要complete函数被调用其生命周期也就结束，再调用next函数不会有反应。subject中的unsubscribe方法调用表示这个subject已经不管事了，之后再调动这个subject对象的next方法就会报错。</p>
<p>subject的错误处理：subject可以把上游的错误传递给下游的Observe，如果subject的某个下游数据流产生了一个错误异常并且这个错误异常没有被observe处理，那么这个subject的其他observe都会失败。</p>
<h3 id="支持多播的操作符"><a href="#支持多播的操作符" class="headerlink" title="支持多播的操作符"></a>支持多播的操作符</h3><ul>
<li><p>multicast：实例操作符，能够以上游的Observable为数据源产生一个新的Hot Observable对象。multicast可以接受一个subject对象作为参数（让multicast实现的其他操作符能够定制行为），如果希望multicast产生的ConnectableObservable对象能够在unsubscribe了上游数据流之后还能再次subscribe（可重用），就必须传递给multicast一个产生subject的工厂函数。multicast返回的Observable对象是Observable子类ConnectableObservable的实例对象，这个Observable对象有一个connect函数，如果不调用connect函数这个ConnectableObservable对象就不会从上游Observable那里获得任何数据。这个connect函数作用就是控制开始多播的时机。ConnectableObservable对象还提供了一个refCount用于自动计数，对observe的个数进行跟踪，当observe数量大于1时订阅上游，当observe数量减少为0时退订上游。multicast第二个可选参数为selector函数，这个函数指定multicast返回的Observable对象生成方法（指定时不生成ConnectableObservable对象），selector函数有一个参数shared（multicast第一个参数代表的subject对象或者调用第一个参数工厂方法产生的subject对象）。</p>
</li>
<li><p>publish：完全通过multicast实现。</p>
<pre><code>function publish(selector) {
    if (selector) {
        return this.multicast(() =&gt; new Subject(),selector);
    }else {
        return this.multicast(new Subject());
    }
}
</code></pre></li>
<li><p>share：返回一个调用了refCount的ConnectableObservable对象，可以自动根据Observer的多少来对上游进行subscribe和unsubscribe。</p>
<pre><code>function shareSubjectFactory(){
    return new Subject();
}
function share () {
    return multicast.call(this,shareSubjectFactory).refCount();
}
</code></pre></li>
</ul>
<h3 id="高级多播功能"><a href="#高级多播功能" class="headerlink" title="高级多播功能"></a>高级多播功能</h3><ul>
<li><p>pulishLast和AsyncSubject：多播的是上游的最后一个数据。</p>
<pre><code>function publishLast() { return multicast.call(this,new AsyncSubject());}
</code></pre><p>AsyncSubject不会将上游的所有数据都转手给下游Observer，而是会记录最后一个数据，当上游Observable完结的时候将这最后一个数据传递给observer。</p>
</li>
<li><p>publishReplay和ReplaySubject</p>
<p>ReplaySubject不只支持多播，而且还能支持录播，能够把上游传下的数据存储下来，当新的observe添加时，让observe能够接收到订阅之前的数据。</p>
<pre><code>function publishReplay(
    bufferSize = Number.POSITIVE_INFINITY, // Replay缓冲区的大小(不指定为最大值)
    windowTime = Number.POSITIVE_INFINITY  // 
    ){
        return multicast.call(this,new ReplaySubject(bufferSize,windowTime));
    }
</code></pre></li>
<li><p>publishBehavior和BehaviorSubject</p>
<pre><code>function publishBehavior(value) {return multicast.call(this,new BehaviorSubject(value));}
</code></pre><p>BehaviorSubject可以提供一个“默认数据”，当添加observe的时候，即使上游还没有吐出数据observe也会立即获得这个“默认数据”，并且这个“默认数据”总是会被上游吐出的最新数据替代。</p>
</li>
</ul>
<hr>



<h2 id="第十一章-掌握时间的Scheduler"><a href="#第十一章-掌握时间的Scheduler" class="headerlink" title="第十一章 掌握时间的Scheduler"></a>第十一章 掌握时间的Scheduler</h2><p>schedule，调度器，用于控制数据流中数据消息的推送节奏。</p>
<h3 id="工作原理"><a href="#工作原理" class="headerlink" title="工作原理"></a>工作原理</h3><ul>
<li><p>调用栈和事件循环</p>
<p>JavaScript引擎：JavaScript的解析和运行环境。</p>
<p>Chrome浏览器和Node.js使用v8，，edge使用的是Chakra引擎，Firefox浏览器使用过Gecko、TraceMonkey等多种引擎。</p>
<p>调用栈（所有编程语言都存在的概念）：当调用一个函数的时候就在调用栈上创建这个函数的运行空间，参数的传递、局部变量的创建都是通过调用栈完成。当一个函数执行完毕的时候，对应调用栈上这个函数的本次运行空间就被清除。</p>
</li>
</ul>
<hr>



<h2 id="第十二章-RxJS的调试和测试"><a href="#第十二章-RxJS的调试和测试" class="headerlink" title="第十二章 RxJS的调试和测试"></a>第十二章 RxJS的调试和测试</h2><p>RxJS中最核心的Observable是一个抽象概念并且多会涉及异步概念，Debugger作用有限。可以通过输出日志<code>console.log</code>实现一样的目的。</p>
<p>可以利用<code>do</code>操作符来帮助调试数据管道，<code>do</code>不会改变数据管道中流过的任何数据，只会流过它并且上游下游都感觉不到do的存在。do可以放在subscribe之前（为了得知什么样的数据会流入observer），如果直接subscribe上游数据流来触发调试代码（即调试代码写在observer中）改变了原有代码的逻辑。</p>
<p>理想情况下利用日志调试应该不影响应用的逻辑、不需要反复修改应用代码、可以控制日志是否输出。可以通过布尔值来控制调试信息：</p>
<pre><code>Observable.prototype.debug = function(fn) {
    if (global.debug){
        return this.do(fn);
    }else {
        return this;
    }
}
const source$ = original$.debug(x =&gt; console.log(&#39;source$ data = &#39;, x));
</code></pre><p>还可以利用一个枚举性质的值来控制，将日志信息分为多个层级：debug、info、warn、error。</p>
<p>理清数据流转方式：程序的数据流依赖图，数据流之间的依赖关系（宏观上）。弹珠图，理清数据转化的过程。</p>
<h3 id="单元测试"><a href="#单元测试" class="headerlink" title="单元测试"></a>单元测试</h3><p>测试是通过努力发现bug来验证代码是否没有缺陷，调试指的是开发者已经发现一个bug，通过跟踪代码的运行过程找到bug的问题根源，然后修改代码让bug消失。单元测试就是提供特定的输入，然后判断是否有预期的输出。</p>
<p>单元测试的好处：</p>
<ul>
<li>能够保证功能代码有正确行为。JavaScript是动态语言，没有静态类型，缺乏类型安全保证（利用typescript或者flow工具也可以做到近似静态语言的类型检查）</li>
<li>提高工作效率，尽可能尽早发现错误。</li>
<li>文档作用，单元测试就是功能代码的使用样例，能够帮助开发者了解如何去使用这些代码。</li>
<li>帮助重构，保障重构没有破坏原有的功能。</li>
</ul>
<p>RxJS贯彻的是函数式编程的思想，单元测试对于函数式编程来说十分容易，每个函数都是纯函数，输出完全由输入决定，也不会因为函数调用改变程序中的其他状态。</p>
<p>传统方式测试RxJS数据流，耗时麻烦，需要控制时间的TestScheduler。</p>
<ul>
<li><p>弹珠测试：利用字符串代替弹珠图，让测试用例中的数据流更清晰。TestScheduler不能重用，每个测试用例的运行都需要创建一个TestScheduler实例，TestScheduler的构造函数接受一个参数来指定判定两个对象是否相等的方法。</p>
<pre><code>const source = &#39;--a--b--|&#39;;
const expected = &#39;--a--b--|&#39;;
</code></pre><p>横杠字符<code>-</code>代表没有数据只有时间流逝，一个字符代表一个数据产生，一个竖杠字符<code>|</code>代表的是数据流的完结。<code>TestScheduler.createColdObservable</code>可以接受source字符串产生对应的Observable对象，可以通过<code>scheduler.expectObservable(source$).toBe(expected)</code>来验证。</p>
<p>字符串弹珠语法：</p>
<p>| 字符         | 说明                                                         |<br>| ———— | ———————————————————— |<br>| -            | 代表时间，一个字符代表10个帧（一帧可以理解为虚拟时间中的一毫秒） |<br>| |           | 代表数据流的完结，也就是Observable调用下游complete函数的时间点 |<br>| #            | 代表数据流抛出错误，也就是Observable调用下游error函数的时间点 |<br>| ()           | 可以括住多个字符，表示这些字符代表的事件同时发生             |<br>| ^            | 代表一个Observable被订阅的时间点                             |<br>| !            | 代表一个Observable被取消订阅的时间点                         |<br>| 任何其他字符 | 代表数据流中的数据，也就是Observable调用下游next函数的内容   |</p>
<p>除了代表完结的<code>|</code>和代表出错的<code>#</code>不占用时间，不管是横杠字符还是字母数字字符都占用10帧。圆括号也占用时间帧，一般在不同代码行中写的字符串只要上下行对齐就容易比对时间。</p>
</li>
</ul>
<p>可测试性代码：体现在可以一次只测试一个功能，很容易制造各种测试前提条件、提高代码的测试覆盖率、模拟被测对象依赖的模块。</p>
<p>编写高可测性代码的一个方法：将逻辑尽量放到数据管道之中，把一些难以单元测试的逻辑推到边缘部分，让数据源和observer的代码尽量简单（简单到无需单元测试）。</p>
<hr>



<h2 id="第十三章-用RxJS驱动React"><a href="#第十三章-用RxJS驱动React" class="headerlink" title="第十三章 用RxJS驱动React"></a>第十三章 用RxJS驱动React</h2><p>用RxJS来管理状态，用React来渲染界面。</p>
<ul>
<li>利用subject作为桥梁</li>
<li>使用高阶组件来接RxJS：用RxJS管理React状态有比较固定的模式，就是通过subject为桥梁来连接RxJS数据流和React组件的状态，可以通过高阶组件将这种通用的模式提取出来，方便多个组件重复使用。这个高阶组件有三个参数：React组件、能产生Observable对象的工厂函数、新产生组件的defaultState。</li>
</ul>
<hr>



<h2 id="第十四章-Redux和RxJS结合"><a href="#第十四章-Redux和RxJS结合" class="headerlink" title="第十四章 Redux和RxJS结合"></a>第十四章 Redux和RxJS结合</h2><p><strong>用RxJS实现Redux</strong>：一种极简方式实现redux库的主要功能<code>action$.scan(reducer,initialState).subscribe(renderView)</code>。<code>action$</code>是一个Observable对象，里面的数就是action对象，一个action对象进入<code>action$</code>之后就会经过scan操作符的处理。每个action对象都会引起应用状态的改变，scan可以调用规约函数reducer，同时还能记住每一次规约的结果，最后只需要订阅scan的输出就能得到应用的当前状态。</p>
<p>redux主要的功能体现在<code>createStore</code>上，调用createStore返回一个对象（具备三个函数dispatch、getState和subscribe），RxJS实现了类似功能createReactiveStore：</p>
<ul>
<li>支持dispatch需要一个能够接受action对象的数据流，使用一个subject对象<code>action$</code>来代表所有action对象，dispatch就是调用<code>action$.next</code>，传入下一个action即可。</li>
<li>支持getState：即获得scan保存的当前状态，由于scan没有暴露api直接访问这个对象可以使用do将状态记录下来（scan要向下游传递当前状态）。</li>
<li>subscribe通过scan产生的数据流。</li>
</ul>
<p>详细实现<a href="https://gitee.com/huangrenhao/jd/tree/master/readingNotes-master/In-depth_explanation_of_RxJS/dissecting-rxjs/chapter-14/redux-rxjs/react-store/src/createReactiveStore.js" target="_blank" rel="noopener">见代码</a>，通过这种方式功能可以和redux相同，react-redux库也可正常运行，createStore与createReactiveStore的差异在于createReactiveStore依赖于RxJS数据流，如果数据流不被订阅整个管道上每个环节的操作都不会运行，react-redux的connect函数实际上做了对store的订阅。</p>
<p>Redux-Observable：Redux与RxJS结合的一种异步处理方案，作为Redux中间件的方式，借助RxJS实现对异步操作的支持。redux-obervable中有个核心概念epic，与reducer类似，接受Observable对象<code>action$</code>（派送给Store的所有action对象组成的Observable对象）和redux的store，返回一个Observable对象。redux只会在启动的时候调用epic一次，之后只会持续给参数<code>action$</code>推送action对象，然后把epic返回的Observable对象中每个数据做dispatch操作。epic函数中通常需要过滤类操作符来过滤掉不相关的action对象。  </p>

      
    </div>
    
    
    

    

    

    

    <footer class="post-footer">
      

      
      
      

      
        <div class="post-nav">
          <div class="post-nav-next post-nav-item">
            
              <a href="/2020/09/28/read-http-the-definitive-guide/" rel="next" title="【读书笔记】HTTP 权威指南">
                <i class="fa fa-chevron-left"></i> 【读书笔记】HTTP 权威指南
              </a>
            
          </div>

          <span class="post-nav-divider"></span>

          <div class="post-nav-prev post-nav-item">
            
              <a href="/2020/09/28/read-webkit-jishuneimu-zhuyongsheng/" rel="prev" title="【读书笔记】WebKit 技术内幕">
                【读书笔记】WebKit 技术内幕 <i class="fa fa-chevron-right"></i>
              </a>
            
          </div>
        </div>
      

      
      
    </footer>
  </div>
  
  
  
  </article>



    <div class="post-spread">
      
    </div>
  </div>


          </div>
          


          

  
    <div class="comments" id="comments">
    </div>
  



        </div>
        
          
  
  <div class="sidebar-toggle">
    <div class="sidebar-toggle-line-wrap">
      <span class="sidebar-toggle-line sidebar-toggle-line-first"></span>
      <span class="sidebar-toggle-line sidebar-toggle-line-middle"></span>
      <span class="sidebar-toggle-line sidebar-toggle-line-last"></span>
    </div>
  </div>

  <aside id="sidebar" class="sidebar">
    
    <div class="sidebar-inner">

      

      
        <ul class="sidebar-nav motion-element">
          <li class="sidebar-nav-toc sidebar-nav-active" data-target="post-toc-wrap">
            文章目录
          </li>
          <li class="sidebar-nav-overview" data-target="site-overview-wrap">
            站点概览
          </li>
        </ul>
      

      <section class="site-overview-wrap sidebar-panel">
        <div class="site-overview">
          <div class="site-author motion-element" itemprop="author" itemscope="" itemtype="http://schema.org/Person">
            
              <p class="site-author-name" itemprop="name">renhao</p>
              <p class="site-description motion-element" itemprop="description"></p>
          </div>

          <nav class="site-state motion-element">

            
              <div class="site-state-item site-state-posts">
              
                <a href="/archives/">
              
                  <span class="site-state-item-count">94</span>
                  <span class="site-state-item-name">日志</span>
                </a>
              </div>
            

            
              
              
              <div class="site-state-item site-state-categories">
                <a href="/categories/index.html">
                  <span class="site-state-item-count">19</span>
                  <span class="site-state-item-name">分类</span>
                </a>
              </div>
            

            

          </nav>

          
            <div class="feed-link motion-element">
              <a href="/atom.xml" rel="alternate">
                <i class="fa fa-rss"></i>
                RSS
              </a>
            </div>
          

          

          
          

          
          

          

        </div>
      </section>

      
      <!--noindex-->
        <section class="post-toc-wrap motion-element sidebar-panel sidebar-panel-active">
          <div class="post-toc">

            
              
            

            
              <div class="post-toc-content"><ol class="nav"><li class="nav-item nav-level-2"><a class="nav-link" href="#第一章-函数响应式编程"><span class="nav-number">1.</span> <span class="nav-text">第一章 函数响应式编程</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#函数式编程"><span class="nav-number">1.1.</span> <span class="nav-text">函数式编程</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#响应式编程"><span class="nav-number">1.2.</span> <span class="nav-text">响应式编程</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#第二章-RxJS入门-以V5版本为例"><span class="nav-number">2.</span> <span class="nav-text">第二章 RxJS入门(以V5版本为例)</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#Observable和Observer"><span class="nav-number">2.1.</span> <span class="nav-text">Observable和Observer</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#退订Observable"><span class="nav-number">2.2.</span> <span class="nav-text">退订Observable</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#Hot-Observable和Cold-Observable"><span class="nav-number">2.3.</span> <span class="nav-text">Hot Observable和Cold Observable</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#操作符简介"><span class="nav-number">2.4.</span> <span class="nav-text">操作符简介</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#弹珠图"><span class="nav-number">2.5.</span> <span class="nav-text">弹珠图</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#第三章-操作符基础"><span class="nav-number">3.</span> <span class="nav-text">第三章 操作符基础</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#map和filter"><span class="nav-number">3.1.</span> <span class="nav-text">map和filter</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#操作符分类"><span class="nav-number">3.2.</span> <span class="nav-text">操作符分类</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#操作符的实现"><span class="nav-number">3.3.</span> <span class="nav-text">操作符的实现</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#第四章-创建数据流"><span class="nav-number">4.</span> <span class="nav-text">第四章 创建数据流</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#创建类操作符"><span class="nav-number">4.1.</span> <span class="nav-text">创建类操作符</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#创建同步数据流的操作符"><span class="nav-number">4.2.</span> <span class="nav-text">创建同步数据流的操作符</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#创建异步数据流的操作符"><span class="nav-number">4.3.</span> <span class="nav-text">创建异步数据流的操作符</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#第五章-合并数据流"><span class="nav-number">5.</span> <span class="nav-text">第五章 合并数据流</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#合并类操作符"><span class="nav-number">5.1.</span> <span class="nav-text">合并类操作符</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#高阶Observable"><span class="nav-number">5.2.</span> <span class="nav-text">高阶Observable</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#第六章-辅助类操作符"><span class="nav-number">6.</span> <span class="nav-text">第六章 辅助类操作符</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#数学类操作符"><span class="nav-number">6.1.</span> <span class="nav-text">数学类操作符</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#条件布尔类操作符"><span class="nav-number">6.2.</span> <span class="nav-text">条件布尔类操作符</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#第七章-过滤数据流"><span class="nav-number">7.</span> <span class="nav-text">第七章 过滤数据流</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#过滤类操作符的模式"><span class="nav-number">7.1.</span> <span class="nav-text">过滤类操作符的模式</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#回压控制"><span class="nav-number">7.2.</span> <span class="nav-text">回压控制</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#第8章-转化数据流"><span class="nav-number">8.</span> <span class="nav-text">第8章 转化数据流</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#映射数据"><span class="nav-number">8.1.</span> <span class="nav-text">映射数据</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#缓存窗口：无损回压控制"><span class="nav-number">8.2.</span> <span class="nav-text">缓存窗口：无损回压控制</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#数据分组"><span class="nav-number">8.3.</span> <span class="nav-text">数据分组</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#累计数据"><span class="nav-number">8.4.</span> <span class="nav-text">累计数据</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#第九章-异常错误处理"><span class="nav-number">9.</span> <span class="nav-text">第九章 异常错误处理</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#JavaScript的异常处理"><span class="nav-number">9.1.</span> <span class="nav-text">JavaScript的异常处理</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#RxJS的异常处理"><span class="nav-number">9.2.</span> <span class="nav-text">RxJS的异常处理</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#第十章-多播"><span class="nav-number">10.</span> <span class="nav-text">第十章 多播</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#Subject"><span class="nav-number">10.1.</span> <span class="nav-text">Subject</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#支持多播的操作符"><span class="nav-number">10.2.</span> <span class="nav-text">支持多播的操作符</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#高级多播功能"><span class="nav-number">10.3.</span> <span class="nav-text">高级多播功能</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#第十一章-掌握时间的Scheduler"><span class="nav-number">11.</span> <span class="nav-text">第十一章 掌握时间的Scheduler</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#工作原理"><span class="nav-number">11.1.</span> <span class="nav-text">工作原理</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#第十二章-RxJS的调试和测试"><span class="nav-number">12.</span> <span class="nav-text">第十二章 RxJS的调试和测试</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#单元测试"><span class="nav-number">12.1.</span> <span class="nav-text">单元测试</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#第十三章-用RxJS驱动React"><span class="nav-number">13.</span> <span class="nav-text">第十三章 用RxJS驱动React</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#第十四章-Redux和RxJS结合"><span class="nav-number">14.</span> <span class="nav-text">第十四章 Redux和RxJS结合</span></a></li></ol></div>
            

          </div>
        </section>
      <!--/noindex-->
      

      

    </div>
  </aside>


        
      </div>
    </main>

    <footer id="footer" class="footer">
      <div class="footer-inner">
        <div class="copyright">&copy; <span itemprop="copyrightYear">2020</span>
  <span class="with-love">
    <i class="fa fa-user"></i>
  </span>
  <span class="author" itemprop="copyrightHolder">renhao</span>

  
    <span class="post-meta-divider">|</span>
    <span class="post-meta-item-icon">
      <i class="fa fa-area-chart"></i>
    </span>
    
      <span class="post-meta-item-text">Site words total count&#58;</span>
    
    <span title="Site words total count">265.9k</span>
  
</div>


  <div class="powered-by">由 <a class="theme-link" target="_blank" href="https://hexo.io">Hexo</a> 强力驱动</div>



  <span class="post-meta-divider">|</span>



  <div class="theme-info">主题 &mdash; <a class="theme-link" target="_blank" href="https://github.com/iissnan/hexo-theme-next">NexT.Muse</a> v5.1.4</div>




        







        
      </div>
    </footer>

    
      <div class="back-to-top">
        <i class="fa fa-arrow-up"></i>
        
      </div>
    

    

  </div>

  

<script type="text/javascript">
  if (Object.prototype.toString.call(window.Promise) !== '[object Function]') {
    window.Promise = null;
  }
</script>









  












  
  
    <script type="text/javascript" src="/lib/jquery/index.js?v=2.1.3"></script>
  

  
  
    <script type="text/javascript" src="/lib/fastclick/lib/fastclick.min.js?v=1.0.6"></script>
  

  
  
    <script type="text/javascript" src="/lib/jquery_lazyload/jquery.lazyload.js?v=1.9.7"></script>
  

  
  
    <script type="text/javascript" src="/lib/velocity/velocity.min.js?v=1.2.1"></script>
  

  
  
    <script type="text/javascript" src="/lib/velocity/velocity.ui.min.js?v=1.2.1"></script>
  

  
  
    <script type="text/javascript" src="/lib/fancybox/source/jquery.fancybox.pack.js?v=2.1.5"></script>
  


  


  <script type="text/javascript" src="/js/src/utils.js?v=5.1.4"></script>

  <script type="text/javascript" src="/js/src/motion.js?v=5.1.4"></script>



  
  

  
  <script type="text/javascript" src="/js/src/scrollspy.js?v=5.1.4"></script>
<script type="text/javascript" src="/js/src/post-details.js?v=5.1.4"></script>



  


  <script type="text/javascript" src="/js/src/bootstrap.js?v=5.1.4"></script>



  


  




	





  





  










  <script src="//cdn1.lncld.net/static/js/3.0.4/av-min.js"></script>
  <script src="//unpkg.com/valine/dist/Valine.min.js"></script>
  
  <script type="text/javascript">
    var GUEST = ['nick','mail','link'];
    var guest = 'nick,mail,link';
    guest = guest.split(',').filter(item=>{
      return GUEST.indexOf(item)>-1;
    });
    new Valine({
        el: '#comments' ,
        verify: false,
        notify: false,
        appId: '2P1xqUWiCoNm14MH4yhURlJi-gzGzoHsz',
        appKey: 'uJN0uagHIVgXtBO1OuLV9Ban',
        placeholder: 'Just go go',
        avatar:'mm',
        guest_info:guest,
        pageSize:'10' || 10,
    });
  </script>



  





  

  

  

  
  

  

  

  

</body>
</html>
